(* Creme Caramel Compiler (Triple C) - Michele Caceffo - 2008 *)

open Exceptions;;
open Syntaxtree;;
open Intermediate;;
open Declaration;;

(** Here are all the functions to generate the intermediate code for the arithmetical and boolean expressions *)

let rec getArrayPointer (stackoff:element) (idx:int) (acc:int) (ic:intermediateCode) = 
        if (acc=idx) then stackoff 
                     else (let reg=ic#getReg in
                           ic#addInstr (GAP,(Reg(reg,DInt)), stackoff,Null);
                           getArrayPointer (Reg(reg,DInt)) idx (acc+1) ic
                          )

(** Creates the instructions for the parameters before a call
    @param plist The parameter list
    @param dect The declarations table
    @param proct The procedures table
    @param ic The instance of the intermediateCode instance
*)
let rec genParam (plist: aexp list) (dect: dec_table) (proct: proc_table) (ic: intermediateCode): unit = match plist with
        []      ->  ()
      | a::ae   ->  genParam ae dect proct ic; ic#addInstr (PARAM, (genArithmetical a dect proct ic), Null, Null)

(** Creates the instruction for the arithmetical expressions 
    @param a The arithmetical expression to analize
    @param dect The declarations table
    @param proct The procedures table
    @param ic The intermediateCode class instance
    @return An element, which can be a Value, an Offset or a temporary register
*)
and genArithmetical (a:aexp) (dect: dec_table) (proct: proc_table) (ic: intermediateCode): element = match a with
     VecSearch(i,x)    -> (*let (idx,dim,Off(off,DInt,b)),rop=(getVecDeclaration i 0 (-1) dect),(genArithmetical x dect proct ic) in
                                     let startwhile,endwhile,result,value,acc = ic#getLabel,ic#getLabel,ic#getReg,ic#getReg,ic#getReg in
                                        (ic#addInstr (CPY, Val(I(1)), Null, Reg(result,DInt));
                                         ic#addInstr (CPY,Val(I(off)) , Null, Reg(acc,DInt));
                                         ic#addLabel startwhile (ic#getIc);
                                         ic#addInstr (JNE, Reg(result,DInt), Val(I(1)), endwhile);
                                         let element = getArrayPointer (Off(off,DInt,b)) (acc) 0 ic in(
                                         ic#addInstr (GAV ,Reg(value,DInt), element, Null);
                                         ic#addInstr (CEQU ,Reg(value,DInt), rop, Reg(result,DInt));
(* reg[1]=0;
reg[2]=stack[reg[1]].a.p;
reg{3}= stack[reg[2]].a.i;
reg[4]=reg[3]==Val;
reg[1]=stack[reg[2]].a.p;
*)                                       
                                         ic#addInstr (GOTO, startwhile, Null, Null);
                                         ic#addLabel endwhile (ic#getIc);
                                         
                                        ))
                                    *)

                           (let (idx,dim,Off(off,DInt,b))=getVecDeclaration i 0 (-1) dect in
                                                             let accumulator,result,value,reg2,reg3=ic#getReg,ic#getReg,ic#getReg,ic#getReg,ic#getReg and  rop2 = genArithmetical x dect proct ic in 
                                                             let startwhile,endwhile,ctrue1,ctrue2,endif = ic#getLabel,ic#getLabel,ic#getLabel,ic#getLabel,ic#getLabel in
                                                               (
                                                                  ic#addInstr (CPY, Val(I(1)), Null, (Reg(result,DInt)));
                                                                  ic#addInstr (CPY, Off(off,DInt,b), Null, (Reg(reg2,DInt)));(*assegno prevantivamente l'offset al resgistro reg2 nel caso devo sostituire il primo elemento*)
                                                                  ic#addLabel startwhile (ic#getIc);
                                                                  
                                                                  ic#addInstr (JNE, Reg(result,DInt), Val(I(1)), ctrue2);
                                                                  
                                                                                                                                     
                                                                  ic#addInstr (CPY,(Reg(reg2,DInt)) , Null, (Reg(reg3,DInt)));
                                                                  ic#addInstr (GAP,(Reg(reg2,DInt)),(Reg(reg3,DInt)) ,Null);
                                                                  ic#addInstr (ADD,(Reg(accumulator,DInt)) , Val(I(1)), (Reg(accumulator,DInt)));
                                                                  ic#addInstr (GAV ,Reg(value,DInt),(Reg(reg2,DInt)), Null);
                                                                  ic#addInstr (JIE, Reg(value,DInt), rop2, ctrue1);
                                                                  ic#addInstr (CLE, Reg(accumulator,DInt), Val(I(dim)),Reg(result,DInt) );
                                                                   
                                                                  ic#addInstr (GOTO, startwhile, Null, Null);
                                                                  

                                                                  
                                                                  
                                                                  ic#addLabel ctrue1 (ic#getIc);
                                                                  ic#addInstr (CPY,(Reg(accumulator,DInt)) , Null, (Reg(result,DInt)));
                                                                   ic#addInstr (GOTO, endif, Null, Null);
                                                                  ic#addLabel ctrue2 (ic#getIc);
                                                                  ic#addInstr (CPY,Val(I(-1)) , Null, (Reg(result,DInt)));
                                                                   
                                                                  ic#addLabel endif (ic#getIc);
                                                                    (Reg(result,DInt))
                                                                  
                                                                 
                                                              )
)
                       

  |  N(n)              ->  Val(I(n))
  | R(n)              ->  Val(F(n))
  | Var(i)            ->  (
                             if(isVarDeclared i dect) then (
                                let (_,_,stackoff) = getVarDeclaration i dect in stackoff
                             ) else raise (NON_DECLARED_VARIABLE "ERROR: Undeclared variable\n")
                            )
    | Vec(i,x)          ->  (*
                             if(isVarDeclared i dect) then (
                                let  (idx, dim, stackoff) = getVecDeclaration i 0 (-1) dect in 
                                 let element = getArrayPointer stackoff (x) 0 ic in
                                  let reg = ic#getReg in (
                                    ic#addInstr (GAV,Reg(reg,DInt),element , Null);   
                                    Reg(reg,DInt))
                                )else raise (NON_DECLARED_VARIABLE "ERROR: Undeclared variable\n")
                                  
                            *)
                            (
                             if(isVarDeclared i dect) then (
                                let  (idx, dim, off) = getVecDeclaration i 0 (-1) dect in 
                                let reg,reg2,reg3,result=ic#getReg,ic#getReg,ic#getReg,ic#getReg and  rop = genArithmetical x dect proct ic in 
                                let startwhile,endwhile = ic#getLabel,ic#getLabel in
                                   (
                                 ic#addInstr (CPY, Val(I(0)), Null, (Reg(reg,DInt)));
                                 ic#addInstr (CPY, off, Null, (Reg(reg2,DInt)));(*assegno prevantivamente l'offset al resgistro reg2 nel caso devo sostituire il primo elemento*)
                                 ic#addLabel startwhile (ic#getIc);
                                 ic#addInstr (CNE ,Reg(reg,DInt), rop, Reg(result,DInt));
                                 ic#addInstr (JNE, Reg(result,DInt), Val(I(1)), endwhile);
                                 ic#addInstr (ADD,(Reg(reg,DInt)) , Val(I(1)), (Reg(reg,DInt)));
                                 ic#addInstr (CPY,(Reg(reg2,DInt)) , Null, (Reg(reg3,DInt)));
                                 ic#addInstr (GAP,(Reg(reg2,DInt)),(Reg(reg3,DInt)) ,Null);
                                 ic#addInstr (GOTO, startwhile, Null, Null);
                                 ic#addLabel endwhile (ic#getIc);
                                  let reg = ic#getReg in (
                                    ic#addInstr (GAV,Reg(reg,DInt),Reg(reg2,DInt) , Null);   
                                    Reg(reg,DInt))
                                ))else raise (NON_DECLARED_VARIABLE "ERROR: Undeclared variable\n")
                                  
                            )

    | Mat(i,x,y)        ->  (
                             if(isVarDeclared i dect) then (
                                let xRes = genArithmetical x dect proct ic and yRes = genArithmetical y dect proct ic and
                                r1,r2,r3 = (ic#getReg),(ic#getReg),(ic#getReg) and
                                (_, dim, stackoff) = getVecDeclaration i 0 0 dect in (
                                  let t = (match stackoff with
                                              Off(_, t, _) -> t
                                            | _ -> raise (CONFIRM_RULE "Cannot find something different from Off, as we already know we have an existing variable\n")
                                           )
                                  in
                                    (
                                    ic#addInstr (MUL, (Val(I(dim))), xRes, (Reg(r1,DInt)));
                                    ic#addInstr (ADD, (Reg(r1,DInt)), yRes, (Reg(r2,DInt)));
                                    if (t = DInt) then
                                      (ic#addInstr (AGET, stackoff, (Reg(r2,DInt)), (Reg(r3,DInt)));
                                      (Reg(r3,DInt)))
                                    else
                                      (ic#addInstr (AGET, stackoff, (Reg(r2,DInt)), (Reg(r3,DFloat)));
                                      (Reg(r3,DFloat)))
                                    )
                                )
                             ) else raise (NON_DECLARED_VARIABLE "ERROR: Undeclared variable\n")
                            )
  | Sum(ex1,ex2)      ->  (
                             let reg = (ic#getReg) in (
                                let op1 = (genArithmetical ex1 dect proct ic)
                                and op2 = (genArithmetical ex2 dect proct ic)
                                in
                                let t = (match op1 with
                                            Off(_, t, _) -> t
                                          | Reg(_, t) -> t
                                          | Val(v) -> ( match v with
                                                           I(_) -> DInt
                                                         | F(_) -> DFloat
                                                      )
                                          | _ -> raise (CONFIRM_RULE "Altro non abbiamo")
                                         )
                                in
                                if t = DInt then (* We check the type for one of the two operands, as the semantic correctness has already been assessed *)
                                  (ic#addInstr(ADD, op1, op2,(Reg(reg,DInt)));
                                  (Reg(reg,DInt)))
                                else
                                  (ic#addInstr(ADD, op1, op2,(Reg(reg,DFloat)));
                                  (Reg(reg,DFloat)))
                             )
                            )
    | Sub(ex1,ex2)      ->  (
                             let reg = (ic#getReg) in (
                                let op1 = (genArithmetical ex1 dect proct ic)
                                and op2 = (genArithmetical ex2 dect proct ic)
                                in
                                let t = (match op1 with
                                            Off(_, t, _) -> t
                                          | Reg(_, t) -> t
                                          | Val(v) -> ( match v with
                                                           I(_) -> DInt
                                                         | F(_) -> DFloat
                                                      )
                                          | _ -> raise (CONFIRM_RULE "Altro non abbiamo")
                                         )
                                in
                                if t = DInt then (* We check the type for one of the two operands, as the semantic correctness has already been assessed *)
                                  (ic#addInstr(SUB, op1, op2,(Reg(reg,DInt)));
                                  (Reg(reg,DInt)))
                                else
                                  (ic#addInstr(SUB, op1, op2,(Reg(reg,DFloat)));
                                  (Reg(reg,DFloat)))
                             )
                            )
  | Mul(ex1,ex2)      ->  (
                             let reg = (ic#getReg) in (
                                let op1 = (genArithmetical ex1 dect proct ic)
                                and op2 = (genArithmetical ex2 dect proct ic)
                                in
                                let t = (match op1 with
                                            Off(_, t, _) -> t
                                          | Reg(_, t) -> t
                                          | Val(v) -> ( match v with
                                                           I(_) -> DInt
                                                         | F(_) -> DFloat
                                                      )
                                          | _ -> raise (CONFIRM_RULE "Altro non abbiamo")
                                         )
                                in
                                if t = DInt then (* We check the type for one of the two operands, as the semantic correctness has already been assessed *)
                                  (ic#addInstr(MUL, op1, op2,(Reg(reg,DInt)));
                                  (Reg(reg,DInt)))
                                else
                                  (ic#addInstr(MUL, op1, op2,(Reg(reg,DFloat)));
                                  (Reg(reg,DFloat)))
                             )
                            )
  | Div(ex1,ex2)      ->  (
                             let reg = (ic#getReg) in (
                                let op1 = (genArithmetical ex1 dect proct ic)
                                and op2 = (genArithmetical ex2 dect proct ic)
                                in
                                let t = (match op1 with
                                            Off(_, t, _) -> t
                                          | Reg(_, t) -> t
                                          | Val(v) -> ( match v with
                                                           I(_) -> DInt
                                                         | F(_) -> DFloat
                                                      )
                                          | _ -> raise (CONFIRM_RULE "Altro non abbiamo")
                                         )
                                in
                                if t = DInt then (* We check the type for one of the two operands, as the semantic correctness has already been assessed *)
                                  (ic#addInstr(DIV, op1, op2,(Reg(reg,DInt)));
                                  (Reg(reg,DInt)))
                                else
                                  (ic#addInstr(DIV, op1, op2,(Reg(reg,DFloat)));
                                  (Reg(reg,DFloat)))
                             )
                            )
    | FCall(Ide(i),plist)   ->  (
                                 let pten = (Hashtbl.find proct (Ide(i))) in match pten with
                                     Building(t, _)     ->  (
                                                                 let reg = (ic#getReg) in (
                                                                 (genParam plist dect proct ic);
                                                                 match t with 
                                                                    SRet(Int)   ->  ic#addInstr (CALL, Subr(i), Null, Reg(reg,DInt));
                                                                                    Reg(reg,DInt)
                                                                  | SRet(Float) ->  ic#addInstr (CALL, Subr(i), Null, Reg(reg,DFloat));
                                                                                    Reg(reg,DFloat)
                                                                  | Void        ->  raise (SEMANTIC_ERROR "Void function")
                                                                 )
                                                                )
                                   | Subroutine(t,_,_,_)    ->  (
                                                                 let reg = (ic#getReg) in (
                                                                 (genParam plist dect proct ic);
                                                                 match t with 
                                                                    SRet(Int)   ->  ic#addInstr (CALL, Subr(i), Null, Reg(reg,DInt));
                                                                                    Reg(reg,DInt)
                                                                  | SRet(Float) ->  ic#addInstr (CALL, Subr(i), Null, Reg(reg,DFloat));
                                                                                    Reg(reg,DFloat)
                                                                  | Void        ->  raise (SEMANTIC_ERROR "Void function")
                                                                 )
                                                                )
                                )
(** Creates the instruction for the boolean expressions 
    @param a The boolean expression to analize
    @param dect The declarations table
    @param proct The procedures table
    @param ic The intermediateCode class instance(genBoolean () dect proct ic)
    @return An element, which can be a Value or a temporary register
*)
and genBoolean (a: bexp) (dect: dec_table) (proct: proc_table) (ic: intermediateCode): element = match a with
    B(b)              ->  if b then (Val(I(1))) else (Val(I(0)))
  | Equ(ex1,ex2)      ->  (
                             let reg = (ic#getReg) and 
                             (t1,t2) = (genArithmetical ex1 dect proct ic),(genArithmetical ex2 dect proct ic) in (
                                 ic#addInstr(CEQU, t1, t2, (Reg(reg,DInt)));
                                 (Reg(reg,DInt))
                             )
                            )
  | NotEqu(ex1,ex2)    ->(
                             let reg = (ic#getReg) and 
                             (t1,t2) = (genArithmetical ex1 dect proct ic),(genArithmetical ex2 dect proct ic) in (
                                 ic#addInstr(CNE, t1, t2, (Reg(reg,DInt)));
                                 (Reg(reg,DInt))
                             )
                            )
  | LE(ex1,ex2)       ->  (
                             let reg = (ic#getReg) and 
                             (t1,t2) = (genArithmetical ex1 dect proct ic),(genArithmetical ex2 dect proct ic) in (
                                 ic#addInstr(CGE, t2, t1, (Reg(reg,DInt)));
                                 (Reg(reg,DInt))
                             )
                            )
  | LT(ex1,ex2)       ->  (
                             let reg = (ic#getReg) and 
                             (t1,t2) = (genArithmetical ex1 dect proct ic),(genArithmetical ex2 dect proct ic) in (
                                 ic#addInstr(CG, t2, t1, (Reg(reg,DInt)));
                                 (Reg(reg,DInt))
                             )
                            )
  | Not(b)            ->  (
                             let reg = (ic#getReg) in (
                                 ic#addInstr(NOT, (genBoolean b dect proct ic), Null, (Reg(reg,DInt)));
                                 (Reg(reg,DInt))
                             )
                            )
  | And(bex1,bex2)    ->  (
                             let reg = (ic#getReg) in (
                                 ic#addInstr(AND, (genBoolean bex1 dect proct ic), (genBoolean bex2 dect proct ic), (Reg(reg,DInt)));
                                 (Reg(reg,DInt))
                             )
                            )
  | Or(bex1,bex2)     ->  (
                             let reg = (ic#getReg) in (
                                 ic#addInstr(OR, (genBoolean bex1 dect proct ic), (genBoolean bex2 dect proct ic), (Reg(reg,DInt)));
                                 (Reg(reg,DInt))
                             )
                            )
